// SPDX-License-Identifier: Mulan PSL v2
/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This software is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

use std::{
    io, thread,
    time::{Duration, Instant},
};

use thiserror::Error;
use tracing::debug;

mod channel;

use crate::sys::{mem, shmem::Shmem};

const S2C_SUFFIX: &str = "_s2c";
const C2S_SUFFIX: &str = "_c2s";

#[derive(Debug, Error)]
pub enum Error {
    // --- Initialization Errors ---
    #[error("Failed to create shmem '{name}'")]
    ShmemCreateError {
        name: String,
        #[source]
        error: io::Error,
    },

    #[error("Failed to open shmem '{name}'")]
    ShmemOpenError {
        name: String,
        #[source]
        error: io::Error,
    },

    #[error("Failed to lock shmem fd")]
    LockFailed(#[source] io::Error),

    #[error("Insufficient shmem for channel header (required: {required}, actual: {capacity})")]
    InvalidShmemSize { required: usize, capacity: usize },

    #[error("Capacity must be a power of two (capacity: {capacity})")]
    InvalidCapacity { capacity: usize },

    #[error("Invalid magic number")]
    InvalidMagic,

    // --- Connection Errors ---
    #[error("Connection timed out")]
    ConnectTimeout,

    #[error("Connection is closed")]
    ConnectionClosed,

    // --- Control Flow ---
    #[error("Operation timed out")]
    Timeout,

    #[error("Operation interrupted")]
    Interrupted,

    // --- Internal Errors ---
    #[error("Read buffer overflow (attempted: {attempted}, capacity: {capacity})")]
    ReadOverflow { attempted: usize, capacity: usize },

    #[error("Write buffer overflow (attempted: {attempted}, capacity: {capacity})")]
    WriteOverflow { attempted: usize, capacity: usize },
}

#[derive(Debug, Clone, Copy)]
pub struct Transport {
    buffer_size: usize,
}

impl Transport {
    #[inline]
    pub fn new(buffer_size: usize) -> Self {
        Self { buffer_size }
    }
}

impl Default for Transport {
    fn default() -> Self {
        Self::new(2 * mem::page_size())
    }
}

impl Transport {
    pub fn create<S: AsRef<str>>(&self, name: S) -> Result<Endpoint, Error> {
        let shmem_name = name.as_ref();
        let header_size = channel::header_size();
        let data_size = self.buffer_size;

        debug!("[Shmem] Hosting '{}'...", shmem_name);
        let tx_name = format!("{shmem_name}{S2C_SUFFIX}");
        let rx_name = format!("{shmem_name}{C2S_SUFFIX}");

        let tx_shmem = Shmem::create(&tx_name, header_size, data_size).map_err(|err| {
            Error::ShmemCreateError {
                name: tx_name,
                error: err,
            }
        })?;
        let rx_shmem = Shmem::create(&rx_name, header_size, data_size).map_err(|err| {
            Error::ShmemCreateError {
                name: rx_name,
                error: err,
            }
        })?;

        let (_, tx_writer) = channel::channel(tx_shmem)?;
        let (rx_reader, _) = channel::channel(rx_shmem)?;

        let endpoint = Endpoint::new(rx_reader, tx_writer);
        debug!("[Shmem] Endpoint: {:#?}", endpoint);
        debug!("[Shmem] Hosted on '{}'.", shmem_name);

        Ok(endpoint)
    }

    pub fn connect<S: AsRef<str>>(&self, name: S, timeout: Duration) -> Result<Endpoint, Error> {
        const CONNECT_POLL_INTERVAL: Duration = Duration::from_millis(50);

        let shmem_name = name.as_ref();
        let header_size = channel::header_size();
        let start = Instant::now();

        debug!("[Shmem] Connecting to '{}'...", shmem_name);

        let tx_name = format!("{shmem_name}{C2S_SUFFIX}");
        let rx_name = format!("{shmem_name}{S2C_SUFFIX}");

        loop {
            if start.elapsed() >= timeout {
                return Err(Error::ConnectTimeout);
            }

            let tx_result = Shmem::open(&tx_name, header_size);
            let rx_result = Shmem::open(&rx_name, header_size);

            match (tx_result, rx_result) {
                (Ok(tx_shmem), Ok(rx_shmem)) => {
                    debug!("[Shmem] Both shmem segments are open, creating channels...");

                    let (_, tx_writer) = channel::channel(tx_shmem)?;
                    let (rx_reader, _) = channel::channel(rx_shmem)?;

                    let endpoint = Endpoint::new(rx_reader, tx_writer);
                    debug!("[Shmem] Endpoint created: {:#?}", endpoint);
                    debug!("[Shmem] Connected to '{}'.", shmem_name);

                    return Ok(endpoint);
                }

                (Err(e), _) if e.kind() != io::ErrorKind::NotFound => {
                    return Err(Error::ShmemOpenError {
                        name: tx_name,
                        error: e,
                    });
                }
                (_, Err(e)) if e.kind() != io::ErrorKind::NotFound => {
                    return Err(Error::ShmemOpenError {
                        name: rx_name,
                        error: e,
                    });
                }
                _ => {
                    thread::sleep(CONNECT_POLL_INTERVAL);
                }
            }
        }
    }
}

#[derive(Debug)]
pub struct Endpoint {
    reader: channel::Reader,
    writer: channel::Writer,
}

impl Endpoint {
    #[inline]
    pub fn new(reader: channel::Reader, writer: channel::Writer) -> Self {
        Self { reader, writer }
    }

    #[inline]
    pub fn read_timeout(&self, timeout: Duration) -> Result<channel::ReadGuard<'_>, Error> {
        self.reader.read_timeout(timeout)
    }

    #[inline]
    pub fn read(&self) -> Result<channel::ReadGuard<'_>, Error> {
        self.reader.read()
    }

    #[inline]
    pub fn write_timeout(&self, timeout: Duration) -> Result<channel::WriteGuard<'_>, Error> {
        self.writer.write_timeout(timeout)
    }

    #[inline]
    pub fn write(&self) -> Result<channel::WriteGuard<'_>, Error> {
        self.writer.write()
    }
}

#[cfg(test)]
mod tests;
